﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Services;
using Roslyn.Services.Editor;
using System.Windows.Media;

using Roslyn.Compilers.CSharp;
using CSharpSyntax = Roslyn.Compilers.CSharp.Syntax;
using CSharpSyntaxTree = Roslyn.Compilers.CSharp.SyntaxTree;
using CSharpSyntaxNode = Roslyn.Compilers.CSharp.SyntaxNode;
using CSharpExtensions = Roslyn.Compilers.CSharp.SyntaxExtensions;

using VisualBasicSyntax = Roslyn.Compilers.VisualBasic.Syntax;
using VisualBasicSyntaxTree = Roslyn.Compilers.VisualBasic.SyntaxTree;
using VisualBasicSyntaxNode = Roslyn.Compilers.VisualBasic.SyntaxNode;
using VisualBasicExtensions = Roslyn.Compilers.VisualBasic.SyntaxExtensions;

using King.Extensions;
using King.Reflection;
using King.SyntaxInfo;
using King.Compiler.Common;
using Microsoft.VisualStudio.Language.Intellisense;

namespace CodeIssue1 {

    public static class Extensions {

        public static string AssemblyQualifiedName(this ITypeSymbol symbol) {
            if (symbol == null)
                return null;

            return symbol.Name;
        }
    }

    internal class CodeAction : ICodeAction {

        private CodeIssueScope m_getIssuesScope;
        private AttributeSyntaxInfo m_attribute;

        public CodeAction(CodeIssueScope getIssuesScope, AttributeSyntaxInfo attribute) {
            m_getIssuesScope = getIssuesScope;
            m_attribute = attribute;
        }

        public ICodeActionEdit GetEdit(CancellationToken cancellationToken) {
            var document = m_getIssuesScope.Document;
            var tree = (CommonSyntaxTree)document.GetSyntaxTree(m_getIssuesScope.Loader.CancellationToken);

            // get order attributes for all members declared in the same type as the target attribute in source code order
            var member = m_attribute.Member;
            var type = member.DeclaringType;
            var members = type != null ? type.Members() : member.Namespace.Types(false);
            var attributes = members.SelectMany(o => o.Attributes());
            var orderAttributes = attributes.Where(o => CodeIssueScope.IsOrderAttribute(o));
            var targets = orderAttributes.Select(o => o.CommonSyntaxNode);

            // preform the transform
            var order = 0;
            var newRoot = tree.Root.ReplaceNodes(targets, (o, x) => UpdateAttribute(o, order++));

            return m_getIssuesScope.EditFactory.CreateTreeTransformEdit(document.Project.Solution, tree, newRoot);
        }

        public CommonSyntaxNode UpdateAttribute(CommonSyntaxNode attribute, int order) {

            dynamic dAttribute = attribute;
            //dynamic dSyntax = new DynamicStaticType(attribute is CSharpSyntaxNode ? typeof(CSharpSyntax) : typeof(VisualBasicSyntax));

            var expression = Syntax.ParseExpression(order.ToString());
            var attributeArgument = Syntax.AttributeArgument(expression: expression);
            var attributeArgumentList = Syntax.AttributeArgumentList(arguments: Syntax.SeparatedList(attributeArgument));
            var newAttribute = dAttribute.Update(dAttribute.Name, attributeArgumentList);
            return newAttribute;
        }

        public string Description {
            get { return "Update Order Attributes"; }
        }
        public ImageSource Icon {
            get { return null; }
        }
    }

    internal class CodeIssueScope {

        internal static bool IsOrderAttribute(AttributeSyntaxInfo attribute) {
            var name = attribute.Type.Name;
            return name.Contains("Order");
        }

        private SyntaxInfoLoader m_loader;
        private ICodeActionEditFactory m_editFactory;
        private IDocument m_document;
        private CancellationToken m_cancellationToken;

        internal CodeIssueScope(ICodeActionEditFactory editFactory, IDocument document, CancellationToken cancellationToken) {
            m_editFactory = editFactory;
            m_document = document;
            m_cancellationToken = cancellationToken;

            var semanticModel = document.GetSemanticModel(cancellationToken);
            m_loader = new SyntaxInfoLoader(semanticModel, cancellationToken);
        }

        private string GetNextIssue(AttributeSyntaxInfo attribute, int? value, bool next) {

            var nextAttribute = attribute.NextAttributes(next).FirstOrDefault();

            // get first or last attribute in next member
            if (nextAttribute == null) {
                var nextMember = attribute.Member.NextMembers(next).FirstOrDefault();
                if (nextMember != null)
                    nextAttribute = nextMember.Attributes().FirstOrDefaultOrLastOrDefault(next);
            }

            // no next or previous attribute
            if (nextAttribute == null)
                return null;
            
            var nextArgument = nextAttribute.Arguments().FirstOrDefaultOrLastOrDefault(next);

            if (nextArgument != null) {
                var nextValue = (int?)nextArgument.Value;

                if (nextValue != null && ((next && value >= nextValue) || (!next && value <= nextValue)))
                    if (next)
                        return string.Format("This member's order of {0} " + 
                            "is greater than or equal to the next member's order of {1}.", value, nextValue);

                    else
                        return string.Format("This member's order of {0} " + 
                            "is less than or equal to the previous member's order of {1}.", value, nextValue);
            }

            return null;
        }

        internal ICodeActionEditFactory EditFactory {
            get { return m_editFactory; }
        }
        internal IDocument Document {
            get { return m_document; }
        }
        internal SyntaxInfoLoader Loader {
            get { return m_loader; }
        }

        internal IEnumerable<CodeIssue> GetIssues(CommonSyntaxNode node) {

            var attribute = Loader.Get<AttributeSyntaxInfo>(node);

            // test for order attribute
            if (!IsOrderAttribute(attribute))
                yield break;

            var argument = attribute.Arguments().FirstOrDefault();

            // get values
            var value = argument != null ? (int?)argument.Value : null;
            if (value == null) {
                yield return new CodeIssue(CodeIssue.Severity.Error, node.Span, string.Empty,
                    new CodeAction(this, attribute));

            } else {

                var descriptions = new List<string>();

                // next issue
                var issue = GetNextIssue(attribute, value, true);
                if (issue != null)
                    descriptions.Add(issue);

                // previous issue
                issue = GetNextIssue(attribute, value, false);
                if (issue != null)
                    descriptions.Add(issue);

                // any issues?
                if (descriptions.Any())
                    yield return new CodeIssue(CodeIssue.Severity.Warning, node.Span,
                        string.Join(Environment.NewLine, descriptions.ToArray()), new CodeAction(this, attribute));
            }
        }
    }

    [ExportCompletionProviderAttribute("Completion", LanguageNames.CSharp)]
    public class CompletionProvider :  ICompletionProvider {

        [Import]
        private IGlyphService m_glyphService;

        //[Import]
        //private ICompletionService m_completionService;

        public class CompletionItem : ICompletionItem {

            private ImageSource m_imageSource;
            private string m_description;
            private string m_displayText;

            public CompletionItem(ImageSource ImageSource, string displayText, string description = null) {
                m_imageSource = ImageSource;
                m_displayText = displayText;
                m_description = description;
            }

            public string Description {
                get { return m_description; }
            }
            public string DisplayText {
                get { return m_displayText; }
            }
            public ImageSource IconSource {
                get { return m_imageSource; }
            }
            public string InsertionText {
                get { return "MyInsertionText"; }
            }
        }

        public IEnumerable<ICompletionItem> GetItems(IDocument document, int position, CancellationToken cancellationToken) {
            try {
                var text = document.GetText(cancellationToken);

                var tree = document.GetSyntaxTree(cancellationToken);
                var token = tree.Root.FindToken(position - 1);
                var node = token.Parent as MemberAccessExpressionSyntax;
                if (node == null)
                    return null;

                var semanticModel = document.GetSemanticModel(cancellationToken);

                var expression = node.Expression;
                var member = expression as MemberAccessExpressionSyntax;
                if (member == null)
                    return null;

                var semanticInfo = semanticModel.GetSemanticInfo(member, cancellationToken);
                var type = semanticInfo.Type;
                if (type.Name != "Object")
                    return null;

                var result = text.GetText(member.Name.Identifier.Span);
                var field = semanticModel.GetSemanticInfo(member.Name);
                var symbol = field.Symbol;
                var attributes = symbol.GetAttributes();

                var glyph = m_glyphService.GetGlyph(
                    StandardGlyphGroup.GlyphGroupProperty,
                    StandardGlyphItem.GlyphItemPublic);

                return new[] { new CompletionItem(glyph, result) };
            } catch {
                return null;
            }
        }
    }

    [ExportSyntaxNodeCodeIssueProvider("CodeIssue1", LanguageNames.CSharp, typeof(AttributeSyntax))]
    class CodeIssueProvider : ICodeIssueProvider {
        private readonly ICodeActionEditFactory m_editFactory;

        [ImportingConstructor]
        public CodeIssueProvider(ICodeActionEditFactory editFactory) {
            m_editFactory = editFactory;
        }

        public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxNode node, CancellationToken cancellationToken) {
            try {
                var scope = new CodeIssueScope(m_editFactory, document, cancellationToken);
                return scope.GetIssues(node);

            } catch {
                return null;
            }
        }
        public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxToken token, CancellationToken cancellationToken) {
            return null;
        }
        public IEnumerable<CodeIssue> GetIssues(IDocument document, CommonSyntaxTrivia trivia, CancellationToken cancellationToken) {
            return null;
        }
    }
}
